/*
 * DrawController.cpp
 *
 *  Created on: Oct 7, 2011
 *      Author: erudhes
 */
#include "DynamicDrawController.h"
#include "IGameController.h"
#include "MainPanel.h"
#include "MainFrame.h"
#include "Configuration.h"
#include "App.h"

DynamicDrawController::DynamicDrawController(SmartPointer<wxBitmap> bitmap, SmartPointer<wxImage> originalLevel,
		                                     SmartPointer<Vehicle> actualVehicle, SmartPointer<Vehicle> remoteVehicle,
		                                     SmartPointer<std::vector<wxPoint> > actualVehicleArrows,
		                                     SmartPointer<std::vector<wxPoint> > remoteVehicleArrows,
		                                     const wxPoint& firstStep): StaticDrawController(bitmap, false),
																		mOriginalLevel(originalLevel),
																		mEarlierHighlightedDot(WxUtils::INVALID_POINT),
																		mEarlierEndPoint(WxUtils::INVALID_POINT),
																		mFirstStep(firstStep),
																		mActualVehicle(actualVehicle),
																		mRemoteVehicle(remoteVehicle),
																		mActualVehicleArrows(actualVehicleArrows),
																		mRemoteVehicleArrows(remoteVehicleArrows),
																		mValidDotsInsideLevel(),
																		mIsMouseButtonDown(false),
																		mIsCrossedStartLine(false)
{
	this->refreshImage(true);
	// Calculate actual valid dots
	this->getValidDotsInsideLevel();
}

/*virtual*/
DynamicDrawController::~DynamicDrawController()
{
	// Nothing to do yet
}

//
// Methods inherited from Controller
//

void DynamicDrawController::handleMouseDown(const wxPoint& /*position*/)
{
	// Store the starting point for later reuse
	if(0 == mActualVehicleArrows->size())
		mActualVehicleArrows->push_back(mActualVehicle->getPosition());
	this->highlightOrEraseValidDots(false);
	mIsMouseButtonDown = true;
}

void DynamicDrawController::handleMouseUp(const wxPoint& position)
{
	if(true == mIsMouseButtonDown)
	{
		wxPoint nextPosition = this->getNearestValidDot(this->getNearestDot(position));
		wxMemoryDC dc;

		if(WxUtils::INVALID_POINT == nextPosition)
		{
			wxGetApp().getMainFrame()->getMainPanel()->getGameController()->finish(IGameController::CRASHED);

			return;
		}
		if(true == this->isGameFinished(nextPosition))
		{
			wxGetApp().getMainFrame()->getMainPanel()->getGameController()->finish(IGameController::FINISHED);

			return;
		}
		mIsMouseButtonDown = false;
		// Erasing the highlighted valid dots
		this->highlightOrEraseValidDots(true);
		mActualVehicle->move(nextPosition);
		// Store the ending point for later reuse
		mActualVehicleArrows->push_back(mActualVehicle->getPosition());
		// Redraw every arrows
		dc.SelectObject(*mBitmap);
		// Erase the earlier highlighted dot if there is any
		if(WxUtils::INVALID_POINT != mEarlierHighlightedDot)
		{
			std::vector<wxPoint> points = WxUtils::getPointsAround(mEarlierHighlightedDot);

			this->setPen(dc, WxUtils::WHITE);
			for(unsigned int i = 0; i < points.size(); i++)
				dc.DrawPoint(points[i]);
		}
		this->setPen(dc, WxUtils::BLUE, 2);
		this->redrawArrows(mActualVehicleArrows, dc);
		this->redrawArrows(mRemoteVehicleArrows, dc);
		// Force a refresh to make the changes visible to the user
		wxGetApp().getMainFrame()->getMainPanel()->Refresh(false);
	}
}

bool DynamicDrawController::handleMouseMotion(const wxPoint& position)
{
	wxPoint dot = this->getNearestDot(position);
	wxPoint validDot(WxUtils::INVALID_POINT);
	wxMemoryDC dc(*mBitmap);

	if(mEarlierHighlightedDot == dot)
	{
		return false;
	}
	// Erase the earlier highlighted dot if there is any
	if(WxUtils::INVALID_POINT != mEarlierHighlightedDot)
	{
		std::vector<wxPoint> points = WxUtils::getPointsAround(mEarlierHighlightedDot);

		this->setPen(dc, WxUtils::WHITE);
		for(unsigned int i = 0; i < points.size(); i++)
			dc.DrawPoint(points[i]);
	}
	// Highlight the dot
	{
		std::vector<wxPoint> points = WxUtils::getPointsAround(dot);

		this->setPen(dc, WxUtils::GREEN);
		for(unsigned int i = 0; i < points.size(); i++)
			dc.DrawPoint(points[i]);
	}
	// Handle the arrow drawing
	if(true == mIsMouseButtonDown)
	{
		validDot = this->getNearestValidDot(dot);

		if(validDot != mEarlierEndPoint)
		{
			std::vector<wxPoint> points;
			wxRect areaToRefresh;
			int dotSize = wxGetApp().getConfiguration()->getDotSize();

			// Erase the earlier drew arrow if there is any
			if(WxUtils::INVALID_POINT != mEarlierEndPoint)
			{
				this->setPen(dc, WxUtils::WHITE, 2);
				dc.DrawLine(mEarlierEndPoint, mActualVehicle->getPosition());
			}
			this->setPen(dc, WxUtils::BLUE, 2);
			dc.DrawLine(validDot, mActualVehicle->getPosition());
			// Before drawing the vehicles we need to deselect the bitmap, because vehicles need to use it as well
			// And only one DC can use a bitmap at a time
			dc.SelectObject(wxNullBitmap);
			// Redraw the vehicles
			mActualVehicle->draw();
			mRemoteVehicle->draw();
			// Redraw the dots and the level
			points.reserve(12);
			points = mActualVehicle->getValidNextPositions();
			points.push_back(mActualVehicle->getPosition());
			points.push_back(mRemoteVehicle->getPosition());
			areaToRefresh = this->getSmallestContainingRect(points);
			dc.SelectObject(*mBitmap);
			for(int i = areaToRefresh.x; i < areaToRefresh.x + areaToRefresh.width; i++)
			{
				for(int j = areaToRefresh.y; j < areaToRefresh.y + areaToRefresh.height; j++)
				{
					bool drawingNeeded = false;

					if(0 == (i % dotSize) && 0 == (j % dotSize))
					{
						this->setPen(dc, WxUtils::RED);
						drawingNeeded = true;
					}
					if(true == this->checkColor(i, j, WxUtils::BLACK))
					{
						this->setPen(dc, WxUtils::BLACK);
						drawingNeeded = true;
					}
					if(true == drawingNeeded)
						dc.DrawPoint(wxPoint(i, j));
				}
			}
			// Redraw every arrows
			this->setPen(dc, WxUtils::BLUE, 2);
			this->redrawArrows(mActualVehicleArrows, dc);
			this->redrawArrows(mRemoteVehicleArrows, dc);
		}
	}
	// Store the dots for later reuse
	mEarlierHighlightedDot = dot;
	mEarlierEndPoint = validDot;
	// Force a refresh to make the changes visible to the user
	wxGetApp().getMainFrame()->getMainPanel()->Refresh(false);

	return true;
}

//
// Methods inherited from DrawController
//

SmartPointer<Vehicle> DynamicDrawController::getActualVehicle() const
{
	return mActualVehicle;
}

SmartPointer<Vehicle> DynamicDrawController::getRemoteVehicle() const
{
	return mRemoteVehicle;
}

SmartPointer<std::vector<wxPoint> > DynamicDrawController::getActualVehicleArrows()
{
	return mActualVehicleArrows;
}

SmartPointer<std::vector<wxPoint> > DynamicDrawController::getRemoteVehicleArrows()
{
	return mRemoteVehicleArrows;
}

/**
 * PRIVATE METHODS
 */

wxPoint DynamicDrawController::getNearestDot(const wxPoint& position) const
{
	wxPoint result;
	int dotSize = wxGetApp().getConfiguration()->getDotSize();
	int modX = position.x % dotSize;
	int modY = position.y % dotSize;

	if(modX < (dotSize / 2))
		result.x = position.x - modX;
	else
		result.x = position.x + (dotSize - modX);
	if(modY < (dotSize / 2))
		result.y = position.y - modY;
	else
		result.y = position.y + (dotSize - modY);
	// If the result is on the edge of the picture move it a bit inner...
	this->alignDot(result);

	return result;
}

wxPoint DynamicDrawController::getNearestValidDot(const wxPoint& position) const
{
	wxPoint result(WxUtils::INVALID_POINT);
	int shortestDistance = -1;

	// If there is no more valid points left, the game is over
	if(0 == mValidDotsInsideLevel.size())
		return result;
	for(unsigned int i = 0; i < mValidDotsInsideLevel.size(); i++)
	{
		int a = 0;
		int b = 0;
		int c = 0;

		a = abs(position.x - mValidDotsInsideLevel[i].x);
		b = abs(position.y - mValidDotsInsideLevel[i].y);
		c = sqrt((a * a) + (b * b));
		if(-1 == shortestDistance)
		{
			shortestDistance = c;
			result = mValidDotsInsideLevel[i];
		}
		else if(c < shortestDistance)
		{
			shortestDistance = c;
			result = mValidDotsInsideLevel[i];
		}
	}
	// If the result is on the edge of the picture move it a bit inner...
	this->alignDot(result);

	return result;
}

void DynamicDrawController::getValidDotsInsideLevel()
{
	std::vector<wxPoint> validDots = mActualVehicle->getValidNextPositions();

	mValidDotsInsideLevel.reserve(validDots.size());
	for(unsigned int index = 0; index < validDots.size(); index++)
	{
		wxPoint v = validDots[index] - mActualVehicle->getPosition();
		int xStart = 0;
		int xEnd = 0;
		bool valid = true;

		// Check if the dots are inside the picture at all
		if(validDots[index].x < 0 || validDots[index].x > mBitmap->GetWidth() || validDots[index].y < 0 || validDots[index].y > mBitmap->GetHeight())
			continue;
		// If the "line" to the dot is crossing the level the dot is considered as invalid
		if(v.x == 0)
		{
			// In this case the line is vertical
			if(mActualVehicle->getPosition().y < validDots[index].y)
			{
				xStart = mActualVehicle->getPosition().y;
				xEnd = validDots[index].y;
			}
			else
			{
				xStart = validDots[index].y;
				xEnd = mActualVehicle->getPosition().y;
			}
			// No need to check the start position it was considered valid earlier
			for(int i = xStart + 1; i <= xEnd; i++)
			{
				if(true == this->checkColor(mActualVehicle->getPosition().x, i, WxUtils::BLACK))
				{
					valid = false;
					break;
				}
			}
		}
		else
		{
			float constant = 0;
			int earlierY = 0;

			constant = (float)v.y / (float)v.x;
			if(mActualVehicle->getPosition().x < validDots[index].x)
			{
				xStart = mActualVehicle->getPosition().x;
				xEnd = validDots[index].x;
				earlierY = mActualVehicle->getPosition().y;
			}
			else
			{
				xStart = validDots[index].x;
				xEnd = mActualVehicle->getPosition().x;
				earlierY = validDots[index].y;
			}
			// No need to check the start position it was considered valid earlier
			for(int i = xStart + 1; i <= xEnd; i++)
			{
				int y = mActualVehicle->getPosition().y + (constant * i) - (constant * mActualVehicle->getPosition().x);
				int yStart = 0;
				int yEnd = 0;

				if(earlierY < y)
				{
					yStart = earlierY;
					yEnd = y;
				}
				else
				{
					yStart = y;
					yEnd = earlierY;
				}
				for(int j = yStart; j <= yEnd; j++)
				{
					if(true == this->checkColor(i, j, WxUtils::BLACK))
					{
						valid = false;
						break;
					}
				}
				if(false == valid)
					break;
				earlierY = y;
			}
		}
		// The position of the other vehicle is also invalid point
		if(false == valid || validDots[index] == mRemoteVehicle->getPosition())
			continue;
		mValidDotsInsideLevel.push_back(validDots[index]);
	}
}

void DynamicDrawController::highlightOrEraseValidDots(bool erase)
{
	wxMemoryDC dc(*mBitmap);

	for(unsigned int i = 0; i < mValidDotsInsideLevel.size(); i++)
	{
		std::vector<wxPoint> points = WxUtils::getPointsAround(mValidDotsInsideLevel[i]);

		for(unsigned int j = 0; j < points.size(); j++)
		{
			if(true == erase)
				this->setPen(dc, WxUtils::WHITE);
			else
				this->setPen(dc, WxUtils::GREEN);
			dc.DrawPoint(points[j]);
		}
	}
}

void DynamicDrawController::alignDot(wxPoint& dot) const
{
	int dotSize = wxGetApp().getConfiguration()->getDotSize();
	wxPoint bottomRightDot = wxPoint(dotSize * (mBitmap->GetWidth() / dotSize), dotSize * (mBitmap->GetHeight() / dotSize));

	if(0 > dot.x)
		dot.x = dotSize;
	if(0 > dot.y)
		dot.y = dotSize;
	if(dot.x > bottomRightDot.x)
		dot.x = bottomRightDot.x - dotSize;
	if(dot.y > bottomRightDot.y)
		dot.y = bottomRightDot.y - dotSize;
}

wxRect DynamicDrawController::getSmallestContainingRect(const std::vector<wxPoint>& points) const
{
	wxPoint topLeft(WxUtils::INVALID_POINT);
	wxPoint bottomRight(WxUtils::INVALID_POINT);
	wxPoint margin(1, 1);

	for(unsigned int i = 0; i < points.size(); i++)
	{
			// Calculate x coordinations
			if(topLeft.x == WxUtils::INVALID_POINT.x)
					topLeft.x = points[i].x;
			else if(points[i].x < topLeft.x)
					topLeft.x = points[i].x;
			if(bottomRight.x == WxUtils::INVALID_POINT.x)
					bottomRight.x = points[i].x;
			else if(points[i].x > bottomRight.x)
					bottomRight.x = points[i].x;
			// Calculate y coordinations
			if(topLeft.y == WxUtils::INVALID_POINT.y)
					topLeft.y = points[i].y;
			else if(points[i].y < topLeft.y)
					topLeft.y = points[i].y;
			if(bottomRight.y == WxUtils::INVALID_POINT.y)
					bottomRight.y = points[i].y;
			else if(points[i].y > bottomRight.y)
					bottomRight.y = points[i].y;
	}
	topLeft -= margin;
	bottomRight += margin;

	return wxRect(topLeft, bottomRight);
}

bool DynamicDrawController::checkColor(int x, int y, const WxUtils::Color& color) const
{
	return (mOriginalLevel->GetRed(x, y) == color.getRed() && mOriginalLevel->GetGreen(x, y) == color.getGreen() && mOriginalLevel->GetBlue(x, y) == color.getBlue());
}

void DynamicDrawController::redrawArrows(std::vector<wxPoint>* arrows, wxDC& dc)
{
	if(arrows->size() > 1)
	{
		wxPoint start = arrows->at(0);
		wxPoint end = arrows->at(1);

		for(unsigned int i = 2; i < arrows->size(); i++)
		{
			dc.DrawLine(start, end);

			start = end;
			end = arrows->at(i);
		}
		dc.DrawLine(start, end);
	}
}

bool DynamicDrawController::isGameFinished(const wxPoint& nextPosition)
{
	const wxString& actualLevel = wxGetApp().getActualLevel();
	wxPoint vehicleAStart =  wxGetApp().getConfiguration()->getLevels()->getLevel(actualLevel)->getVehicleA()->getPosition();
	wxPoint vehicleBStart =  wxGetApp().getConfiguration()->getLevels()->getLevel(actualLevel)->getVehicleB()->getPosition();
	wxPoint actualPosition = mActualVehicle->getPosition();

	if(mFirstStep == WxUtils::INVALID_POINT)
	{
		mFirstStep = nextPosition;

		return false;
	}
	// If the vehicle is not in the the line of the starting line the game is not finished for sure
	if(false == this->isTheActualVehicleInTheStartingLine(nextPosition))
		return false;
	// Check if the starting line is horizontal or vertical
	if(vehicleAStart.x == vehicleBStart.x)
	{
		// Check where was the first step pointing to
		if(mFirstStep.x < vehicleAStart.x)
		{
			// If we are coming from the other side of the start line
			if(actualPosition.x > vehicleAStart.x)
			{
				// If we are crossing the start line
				if(nextPosition.x < vehicleAStart.x)
				{
					if(false == mIsCrossedStartLine)
						return true;
					if(true == mIsCrossedStartLine)
						mIsCrossedStartLine = false;
				}

				// We did not cross the start line, or mIsCrossedStartLine was true
				return false;
			}
			// Here actualPosition.x < vehicleAStart.x
			// If we are crossing the start line from the wrong side
			if(nextPosition.x > vehicleAStart.x)
				mIsCrossedStartLine = true;

			return false;
		}
		else
		{
			// If we are coming from the other side of the start line
			if(actualPosition.x < vehicleAStart.x)
			{
				// If we are crossing the start line
				if(nextPosition.x > vehicleAStart.x)
				{
					if(false == mIsCrossedStartLine)
						return true;
					if(true == mIsCrossedStartLine)
						mIsCrossedStartLine = false;
				}

				// We did not cross the start line, or mIsCrossedStartLine was true
				return false;
			}
			// Here actualPosition.x > vehicleAStart.x
			// If we are crossing the start line from the wrong side
			if(nextPosition.x < vehicleAStart.x)
				mIsCrossedStartLine = true;

			return false;
		}
	}
	else
	{
		// Check where was the first step pointing to
		if(mFirstStep.y < vehicleAStart.y)
		{
			// If we are coming from the other side of the start line
			if(actualPosition.y > vehicleAStart.y)
			{
				// If we are crossing the start line
				if(nextPosition.y < vehicleAStart.y)
				{
					if(false == mIsCrossedStartLine)
						return true;
					if(true == mIsCrossedStartLine)
						mIsCrossedStartLine = false;
				}

				// We did not cross the start line, or mIsCrossedStartLine was true
				return false;
			}
			// Here actualPosition.y < vehicleAStart.y
			// If we are crossing the start line from the wrong side
			if(nextPosition.y > vehicleAStart.y)
				mIsCrossedStartLine = true;

			return false;
		}
		else
		{
			// If we are coming from the other side of the start line
			if(actualPosition.y < vehicleAStart.y)
			{
				// If we are crossing the start line
				if(nextPosition.y > vehicleAStart.y)
				{
					if(false == mIsCrossedStartLine)
						return true;
					if(true == mIsCrossedStartLine)
						mIsCrossedStartLine = false;
				}

				// We did not cross the start line, or mIsCrossedStartLine was true
				return false;
			}
			// Here actualPosition.y > vehicleAStart.y
			// If we are crossing the start line from the wrong side
			if(nextPosition.y < vehicleAStart.y)
				mIsCrossedStartLine = true;

			return false;
		}
	}
}

bool DynamicDrawController::isTheActualVehicleInTheStartingLine(const wxPoint& nextPosition)
{
	const wxString& actualLevel = wxGetApp().getActualLevel();
	const wxPoint& vehicleAStart =  wxGetApp().getConfiguration()->getLevels()->getLevel(actualLevel)->getVehicleA()->getPosition();
	const wxPoint& vehicleBStart =  wxGetApp().getConfiguration()->getLevels()->getLevel(actualLevel)->getVehicleB()->getPosition();
	const wxPoint& actualPosition = mActualVehicle->getPosition();
	wxPoint startLineSmaller = WxUtils::INVALID_POINT;
	wxPoint startLineBigger = WxUtils::INVALID_POINT;

	// Check if the starting line is horizontal or vertical
	if(vehicleAStart.x == vehicleBStart.x)
	{
		unsigned int startYSmaller = 0;
		unsigned int startYBigger = 0;

		// Calculate the crossing points of the starting line and the level
		if(vehicleAStart.y < vehicleBStart.y)
		{
			startYSmaller = vehicleAStart.y;
			startYBigger = vehicleBStart.y;
		}
		else
		{
			startYSmaller = vehicleBStart.y;
			startYBigger = vehicleAStart.y;
		}
		for(unsigned int i = startYSmaller - 1; i != 0; i--)
		{
			if(true == this->checkColor(vehicleAStart.x, i, WxUtils::BLACK))
			{
				startLineSmaller = wxPoint(vehicleAStart.x, i);
				break;
			}
		}
		for(int i = startYBigger + 1; i != mBitmap->GetHeight(); i++)
		{
			if(true == this->checkColor(vehicleAStart.x, i, WxUtils::BLACK))
			{
				startLineBigger = wxPoint(vehicleAStart.x, i);
				break;
			}
		}
		wxASSERT_MSG(startLineSmaller != WxUtils::INVALID_POINT && startLineBigger != WxUtils::INVALID_POINT, wxT("Could not determine the end point of the start line!"));
		// If the vehicle "in front of" the starting line, return true
		if((actualPosition.y > startLineSmaller.y && actualPosition.y < startLineBigger.y) || (nextPosition.y > startLineSmaller.y && nextPosition.y < startLineBigger.y))
			return true;

		return false;
	}
	else
	{
		// In this case the y coordinates must be equal
		unsigned int startXSmaller = 0;
		unsigned int startXBigger = 0;

		// Calculate the crossing points of the starting line and the level
		if(vehicleAStart.x < vehicleBStart.x)
		{
			startXSmaller = vehicleAStart.x;
			startXBigger = vehicleBStart.x;
		}
		else
		{
			startXSmaller = vehicleBStart.x;
			startXBigger = vehicleAStart.x;
		}
		for(unsigned int i = startXSmaller - 1; i != 0; i--)
		{
			if(true == this->checkColor(i, vehicleAStart.y, WxUtils::BLACK))
			{
				startLineSmaller = wxPoint(i, vehicleAStart.y);
				break;
			}
		}
		for(int i = startXBigger + 1; i != mBitmap->GetWidth(); i++)
		{
			if(true == this->checkColor(i, vehicleAStart.y, WxUtils::BLACK))
			{
				startLineBigger = wxPoint(i, vehicleAStart.x);
				break;
			}
		}
		wxASSERT_MSG(startLineSmaller != WxUtils::INVALID_POINT && startLineBigger != WxUtils::INVALID_POINT, wxT("Could not determine the end point of the start line!"));
		// If the vehicle "in front of" the starting line, return true
		if((actualPosition.x > startLineSmaller.x && actualPosition.x < startLineBigger.x) || (nextPosition.x > startLineSmaller.x && nextPosition.x < startLineBigger.x))
			return true;

		return false;
	}

	return false;
}
